package wator.model;

import java.util.Map;
import java.util.Observable;
import java.util.Observer;

import wator.model.agents.EnergySharkAgent;


/**
 * This is the super class for all agent implementation, i.e. all types of
 * individuals living on wator. Implementing sub classes define the behaviors
 * regarding eating, locomotion, breading, and natural causes of deaths such 
 * as high age or starvation.
 * 
 * @see #eats(wator.model.Neighborhood.Neighbor)
 * @see #move(Neighborhood)
 * @see #breed(Neighborhood)
 * @see #dies(Neighborhood)
 */
public abstract class Agent implements Cloneable, Comparable<Agent>,
			Observer {
	
	/**
	 * The position vector of this agent.
	 */
	protected IntegerVector pos;
	
	/**
	 * The direction vector of this agent. This defines the direction and the
	 * velocity of this agent.
	 */
	protected IntegerVector dir;

	/**
	 * Initializes the direction and position vector with the null vector.
	 */
	public Agent() {
		pos = IntegerVector.getCubeVector(Simulation.getInstance()
				.getSizeVector().getDimension(), 0);
		dir = pos;
	}
	
	/**
	 * Copy constructor. The direction and position vector are taken from 
	 * another agent instance. If <code>agent</code> is null, position and
	 * direction vectors are initialized with the null vector.
	 * 
	 * @param template the agent from which position and direction vectors 
	 * 				should be taken (not copied)
	 */
	public Agent(Agent agent) {
		if (agent == null) {
			pos = IntegerVector.getCubeVector(Simulation.getInstance()
					.getSizeVector().getDimension(), 0);
			dir = pos;
		} else {
			pos = agent.pos;
			dir = agent.dir;
		}
	}
	
	/**
	 * Get the energy the fish gives to an energy driven shark when it gets
	 * eaten. Subclasses may change or parameterize this value, or make it 
	 * even dynamically changing round per round. The default implementation
	 * returns <code>Constants.DEFAULT_ENERGY</code> all the time.
	 * 
	 * @see EnergySharkAgent
	 * @see Constants#DEFAULT_ENERGY
	 */
	public int getEnergy() {
		return Constants.DEFAULT_ENERGY;
	}
	
	/**
	 * Implement this to define whether a neighboring individual should be
	 * eaten or not. An easy implementation would look up if the given
	 * individual is on our menu and eat unconditionally in case. Implementing
	 * subclasses are required to adjust internal parameters (like energy
	 * level) here if there are any. This method is called for each agent in
	 * the neighborhood. The default implementation eats if the neigbor is a
	 * prey.
	 * 
	 * @param prey the potential prey
	 * @return true if the prey gets eaten, false if not
	 * @throws NullPointerException if <code>prey</code> is null
	 */
	public boolean eats(Neighborhood.Neighbor prey) {
		return isOnMenu(prey.getAgent());
	}
	
	/**
	 * This method defines the menu of the species. In contrast to
	 * {@link #eats(wator.model.Neighborhood.Neighbor)}, calling this method
	 * has no impact to the population -- the agent does not eat the prey,
	 * but just returns <code>true</code> if it would under some circumstances.
	 * This function may be called by movement strategies, but not by the
	 * simulation procedure itself. The default implementation returns <code>
	 * false</code> unconditionally.
	 * 
	 * @param prey the potential prey
	 * @return true if the prey is on the menu, false otherwise
	 * @throws NullPointerException if <code>prey</code> is null
	 */
	public boolean isOnMenu(Agent prey) {
		return false;
	}
	
	/**
	 * This method defines the locomotion behavior of the agent. The updated
	 * direction vector should be returned. The decision can be made on basis
	 * of the neighboring individuals of all types. This method is called
	 * exactly one time per round. Implementer are also requested to check if 
	 * there is already another individual in the cell to go.
	 *  
	 * @param neighborhood the agents in the neighborhood of this agent. This
	 * 		may also include individuals from another species
	 * @return the update direction vector
	 * @see Agent#dir
	 */
	public abstract IntegerVector move(Neighborhood neighborhood);
	
	/**
	 * This method defines the breeding behavior of the agent. If the agent 
	 * does not breed, this method returns <code>null</code>. Otherwise, this
	 * method returns the new bred individual. The position vector and 
	 * direction vector should be copied, but other internal parameters may 
	 * differ. Note that if the direction vector of the parent is 0 in this
	 * round, the parent dies immediately in case of breeding.
	 * 
	 * @param neighborhood the agents in the neighborhood of this agent. This
	 * 		may also include individuals from another species
	 * @return null if the agent does not breed, a new agent instance otherwise
	 */
	public abstract Agent breed(Neighborhood neighborhood);
	
	/**
	 * This method returns TRUE when the individual dies in this round because 
	 * of a natural death such as high age or starvation. Other implementations
	 * are possible, i.e. over population.
	 * 
	 * @param neighborhood the agents in the neighborhood of this agent. This
	 * 		may also include individuals from another species
	 * @return TRUE if the agent dies, FALSE if not
	 */
	public abstract boolean dies(Neighborhood neighborhood);
	
	/**
	 * Set a parameter value. This normally changes static class variables
	 * in implementing subclasses.
	 * 
	 * @param key name of the parameter
	 * @param value of the parameter
	 * @see #getSpeciesParameters()
	 */
	abstract public void setSpeciesParameter(String key, int value);
	
	/**
	 * Get a map containing all parameters and their current values. This 
	 * normally reflects static variables of implementing subclasses.
	 * 
	 * @return a map with parameters
	 * @see #setSpeciesParameter(String, int)
	 */
	abstract public Map<String, Integer> getSpeciesParameters();
	
	/**
	 * Set the movement strategy, which can be used in 
	 * {@link #move(Neighborhood)}. This value should be stored in static class
	 * variables of implementing subclasses. Note that just setting the 
	 * strategy is not sufficient, {@link #move(Neighborhood)} has to be 
	 * implemented accordingly.
	 * 
	 * @param strategy the new movement strategy
	 */
	abstract public void setMovementStrategy(MovementStrategy strategy);
	
	/**
	 * Get the current movement strategy for this species.
	 * 
	 * @return the used movement strategy for all individuals of this species
	 * @see #setMovementStrategy(MovementStrategy)
	 */
	abstract public MovementStrategy getMovementStrategy();
	
	/**
	 * Set the drawing strategy for drawing individuals of this species. This 
	 * value should be stored in static class variables of implementing 
	 * subclasses. Note that just setting the strategy is not sufficient,
	 * {@link #draw()} has to be implemented accordingly.
	 * 
	 * @param strategy the new drawing strategy
	 */
	abstract public void setDrawingStrategy(DrawingStrategy strategy);
	
	/**
	 * Draws the individual. This should, but not have to be delegated to
	 * the drawing strategy.
	 * 
	 * @see #setDrawingStrategy(DrawingStrategy)
	 */
	abstract public void draw();
	
	/**
	 * Get the name of the species.
	 * 
	 * @return a string representation of the species of this agent
	 */
	abstract public String getSpeciesName();
	
	/**
	 * Returns true if the species should be enabled per default, and false
	 * otherwise. The standard implementation returns false unconditionally.
	 * 
	 * @return the default value of the enabled flag
	 * @see AgentConfiguration#isEnabled()
	 */
	public boolean isDefaultEnabled() {
		return false;
	}

	/**
	 * Get the position vector of this individual.
	 * 
	 * @return the position vector
	 */
	public IntegerVector getPositionVector() {
		return pos;
	}

	/**
	 * Set the position vector of this individual.
	 * 
	 * @param pos the new position vector
	 */
	public void setPositionVector(IntegerVector pos) {
		this.pos = pos;
	}

	/**
	 * Get the direction vector of this individual.
	 * 
	 * @return the direction vector
	 */
	public IntegerVector getDirectionVector() {
		return dir;
	}

	/**
	 * Set the direction vector of this individual.
	 * 
	 * @param dir the new direction vector
	 */
	public void setDirectionVector(IntegerVector dir) {
		this.dir = dir;
	}

	/* (non-Javadoc)
	 * @see java.lang.Object#clone()
	 */
	@Override
	abstract protected Object clone();

	/* (non-Javadoc)
	 * @see java.lang.Comparable#compareTo(java.lang.Object)
	 */
	@Override
	public int compareTo(Agent o) {
		return pos.compareTo(o.pos);
	}

	/**
	 * The template individuals are registered as observers of simulation by
	 * the AvailableAgents class. If the size vector changes, the templates
	 * are notified and they can adjust their position and direction vectors.
	 * 
	 * @see java.util.Observer#update(java.util.Observable, java.lang.Object)
	 * @see Simulation#setSizeVector(IntegerVector)
	 */
	@Override
	public void update(Observable arg0, Object arg1) {
		if (arg1 instanceof IntegerVector) {
			pos = IntegerVector.getCubeVector(((IntegerVector) arg1)
					.getDimension(), 0);
			dir = pos;
		}
	}
}
